盒子
盒子
文章目录
  1. large bin attack:
  2. unsort bin attack:
  3. Tricks:

Glibc Heap Sum

大循环:

  • 后序遍历unsort bin,从main_area的bk指针指向chunk开始遍历,先取出,遍历一个取出一个,取出看该chunk的size,如果大小正好就是所需要的大小,那么便直接拿去使用,结束循环遍历。(后面的就不管了,保持原样
  • 如果大小不是正好,那么便放到相应的bin中去,如果是small bin就放到small bin,是large bin就放到large bin(fast bin被包含在small bin里面了
  • 遍历所有chunk遍历完之后,由于没有一个大小正好的chunk,那么便到刚刚所分配到不同bin中去寻找,如果size是属于small bin的,那么便到small bin中去寻找,如果有比需要的size更大的chunk存在的话,那么就切割,切割剩的一部分如果不小于0x10的话就放到unsort bin中去,如果小于就一起给了。如果small bin当中没有比需要size更大的chunk存在,那么就往large bin中去寻找了(就是在small bin中切割的情况。
  • 在large bin里后序遍历(bk指针开始)。如果找到了比需要size大的chunk,那么也同样切割,切割剩下的一部分不小于0x10则放到unsort bin中去,小于则一起给了。
  1. 超过fast bin大小的chunk被释放后会先进入到unsort bin当中去,并且检测是否有unlink,有则进行unlink再free
  2. small bin和unsort bin和large bin取chunk时都是取反向遍历(bk)链表。

Large Bin细节:

看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

可以看见从unsort bin卸下进入large bin的时候先是设置的fd_nextsize和bk_nextsize再去设置的fd和bk,而且新插入的size用fd_nextsize往前移位插入,值得注意的是当遇到size相同的时候只会插入到后面,并且fd_nextsize和bk_nextsize都不变即不操作,目的就是为了脱链的时候可以减少fd_nextsize和bk_nextsize的操作。用fd_nextsize往前移位插入就是可以避免多次移位遇到size相同的chunk。

large bin attack:

  1. 题目:西湖论剑-storm
  2. 往large bin里面插入chunk的时候是正向遍历(fd),直到找到size比他小的就链入他bk处。
  3. 从large bin里面取chunk的时候是反向遍历(bk),直到找到size比他大的就取出来切割。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
} else {
// large bin 范围
victim_index = largebin_index(size);
bck = bin_at(av, victim_index); // 当前 large bin 的头部
fwd = bck->fd;

/* maintain large bins in sorted order */
/* 从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。
同样大小的 chunk,后来的只会插入到之前同样大小的 chunk 后,
而不会修改之前相同大小的fd/bk_nextsize,这也很容易理解,
可以减低开销。此外,bin 头不参与 nextsize 链接。*/
// 如果 large bin 链表不空
if (fwd != bck) {
/* Or with inuse bit to speed comparisons */
// 加速比较,应该不仅仅有这个考虑,因为链表里的 chunk 都会设置该位。
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
// bck-bk 存储着相应 large bin 中最小的chunk。
// 如果遍历的 chunk 比当前最小的还要小,那就只需要插入到链表尾部。
// 判断 bck->bk 是不是在 main arena。
assert(chunk_main_arena(bck->bk));
if ((unsigned long) (size) <
(unsigned long) chunksize_nomask(bck->bk)) {
// 令 fwd 指向 large bin 头
fwd = bck;
// 令 bck 指向 largin bin 尾部 chunk
bck = bck->bk;
// victim 的 fd_nextsize 指向 largin bin 的第一个 chunk
victim->fd_nextsize = fwd->fd;
// victim 的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize
victim->bk_nextsize = fwd->fd->bk_nextsize;
// 原来链表的第一个 chunk 的 bk_nextsize 指向 victim
// 原来指向链表第一个 chunk 的 fd_nextsize 指向 victim
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
} else {
// 当前要插入的 victim 的大小大于最小的 chunk
// 判断 fwd 是否在 main arena
assert(chunk_main_arena(fwd));
// 从链表头部开始找到不比 victim 大的 chunk
while ((unsigned long) size < chunksize_nomask(fwd)) {
fwd = fwd->fd_nextsize;
assert(chunk_main_arena(fwd));
}
// 如果找到了一个和 victim 一样大的 chunk,
// 那就直接将 chunk 插入到该chunk的后面,并不修改 nextsize 指针。
if ((unsigned long) size ==
(unsigned long) chunksize_nomask(fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else {
// 如果找到的chunk和当前victim大小不一样
// 那么就需要构造 nextsize 双向链表了
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else
// 如果空的话,直接简单使得 fd_nextsize 与 bk_nextsize 构成一个双向链表即可。
victim->fd_nextsize = victim->bk_nextsize = victim;
}
// 放到对应的 bin 中,构成 bck<-->victim<-->fwd。
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

attack攻击操作:

1
2
3
4
5
6
7
8
9
10
11
12
victim->fd_nextsize              = fwd;
victim->bk_nextsize = fwd->bk_nextsize;//1
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//2

bck = fwd->bk;//3

mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//4

导致有两处任意地址写操作:

1和2合并可以:fwd->bk_nextsize->fd_nextsize = victim;

3和4合并可以:fwd->bk->fd = victim;

两处写操作,看自己咋利用了,一般是拿来错位构造0x56大小的chunk。

unsort bin attack:

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

控制了bk的值就能将 unsorted_chunks (av) 写到任意地址。一般拿来结合利用fast bin attack。

这里需要注意的一个点就是,house of orange也用的是unsortbin attack,但是他是在发生attack之后还往后序遍历了,所以说会报错,利用报错来执行IO_file攻击,这跟单纯的利用unosrtbin attack还是有所区别的,所以想改global_max_fast的时候,只需要add一个size相等的chunk即可,因为add的size相等,发生unsortbin attack之后该chunk就被取出来了,不会后续遍历报错了。所以上面的house of orange就是add了一个size不等的chunk导致后续还用bk遍历了。

同样的,large bin attack要结合mmap利用的话也是利用到了bk的后续遍历的。

Tricks:

  1. 可以利用free一个unsortbin,再add来得到libc上的地址。
  2. 也可以利用free两个fastbin,再add来得到heap上的地址。
  3. 一般题都会用的错位构造两个ptr指向同一个chunk来后续利用。
  4. global_max_fast之后可以利用fastbin_index_overflow来改libc上的任意位置为chunk的堆地址,其实就是main_area上的fastbin_index越界了,因为fastbin可以很大。
  5. 当libc地址上的任意一块可以被改写为fastbin的heap头地址的时候,delete掉他之后,再去改写该chunk的fd为任意东西,当再add之后,那块libc地址上的内容就变为了该chunk的fd,即可以任意写。(可以改写__free_hooksystem,当然需要结合4一起用)
  6. setcontext处有一块可以改变栈空间的gadgets(这地方在libc上)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
pwndbg> disassemble &setcontext 
Dump of assembler code for function setcontext:
0x00007f1d4bf64b40 <+0>: push rdi
0x00007f1d4bf64b41 <+1>: lea rsi,[rdi+0x128]
0x00007f1d4bf64b48 <+8>: xor edx,edx
0x00007f1d4bf64b4a <+10>: mov edi,0x2
0x00007f1d4bf64b4f <+15>: mov r10d,0x8
0x00007f1d4bf64b55 <+21>: mov eax,0xe
0x00007f1d4bf64b5a <+26>: syscall
0x00007f1d4bf64b5c <+28>: pop rdi
0x00007f1d4bf64b5d <+29>: cmp rax,0xfffffffffffff001
0x00007f1d4bf64b63 <+35>: jae 0x7f1d4bf64bc0 <setcontext+128>
0x00007f1d4bf64b65 <+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x00007f1d4bf64b6c <+44>: fldenv [rcx]
0x00007f1d4bf64b6e <+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
***0x00007f1d4bf64b75 <+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x00007f1d4bf64b7c <+60>: mov rbx,QWORD PTR [rdi+0x80]
0x00007f1d4bf64b83 <+67>: mov rbp,QWORD PTR [rdi+0x78]
0x00007f1d4bf64b87 <+71>: mov r12,QWORD PTR [rdi+0x48]
0x00007f1d4bf64b8b <+75>: mov r13,QWORD PTR [rdi+0x50]
0x00007f1d4bf64b8f <+79>: mov r14,QWORD PTR [rdi+0x58]
0x00007f1d4bf64b93 <+83>: mov r15,QWORD PTR [rdi+0x60]
0x00007f1d4bf64b97 <+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x00007f1d4bf64b9e <+94>: push rcx
0x00007f1d4bf64b9f <+95>: mov rsi,QWORD PTR [rdi+0x70]
0x00007f1d4bf64ba3 <+99>: mov rdx,QWORD PTR [rdi+0x88]
0x00007f1d4bf64baa <+106>: mov rcx,QWORD PTR [rdi+0x98]
0x00007f1d4bf64bb1 <+113>: mov r8,QWORD PTR [rdi+0x28]
0x00007f1d4bf64bb5 <+117>: mov r9,QWORD PTR [rdi+0x30]
0x00007f1d4bf64bb9 <+121>: mov rdi,QWORD PTR [rdi+0x68]
0x00007f1d4bf64bbd <+125>: xor eax,eax
0x00007f1d4bf64bbf <+127>: ret
0x00007f1d4bf64bc0 <+128>: mov rcx,QWORD PTR [rip+0x37c2b1] # 0x7f1d4c2e0e78
0x00007f1d4bf64bc7 <+135>: neg eax
0x00007f1d4bf64bc9 <+137>: mov DWORD PTR fs:[rcx],eax
0x00007f1d4bf64bcc <+140>: or rax,0xffffffffffffffff
0x00007f1d4bf64bd0 <+144>: ret
End of assembler dump.

加星处,不过后续的push rcx;ret可以用__morecore来平衡。

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/2xg 0x7f1d4c2e23b0
0x7f1d4c2e23b0 <__morecore>: 0x00007f1d4bfa48c0 0x00007f1d4bfa6140
pwndbg> disassemble 0x00007f1d4bfa48c0
Dump of assembler code for function __GI___default_morecore:
0x00007f1d4bfa48c0 <+0>: sub rsp,0x8
0x00007f1d4bfa48c4 <+4>: call 0x7f1d4c019e80 <__GI___sbrk>
0x00007f1d4bfa48c9 <+9>: mov edx,0x0
0x00007f1d4bfa48ce <+14>: cmp rax,0xffffffffffffffff
0x00007f1d4bfa48d2 <+18>: cmove rax,rdx
0x00007f1d4bfa48d6 <+22>: add rsp,0x8
0x00007f1d4bfa48da <+26>: ret
End of assembler dump.

这一点一般都结合2.24及之后_IO_FILEglibc版本使用了,因为刚好可以控制rdi,用了这个gadgets也可以控制rsp了。

  1. _dl_open_hook__free_hook差不多,只是_dl_open_hook不为空时是执行**_dl_open_hook的,而__free_hook是执行*__free_hook,而且_dl_open_hook触发是需要malloc或者free出错,即double free等等这种错误。
  2. _IO_FILE里面需要泄漏的时候改写的_IO_2_1_stdoutflag需要满足的条件是:
1
if f->flag & 0xa00 and f->flag & 0x1000 == 1 then it will leak something when f->write_base != f->write_ptr

而且泄漏范围是从write_basewrite_ptr。(flag并不非要0xfbad起始)

  1. _IO_FILE其实2.23之前随便折腾,2.24开始(包括)都不能改变vtable表,反而需要把他指向_IO_str_jmps,利用调用其中的函数来构造并且触发我们想要的函数。当然前提也需要把_IO_list_all给改了,FSOP是劫持了_IO_list_all并且是把vtable也给改了。
支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫